package client.net;

import java.io.File;
import java.io.IOException;
import java.util.Vector;

import javax.media.Format;
import javax.media.format.AudioFormat;
import javax.swing.event.EventListenerList;

import client.audio.FileAudio;
import client.event.NewDataReceivedEvent;
import client.event.NewStreamEvent;
import client.event.SessionManagerListener;
import client.event.StreamErrorEvent;

/**
 * @author primianoc
 *
 */
public class SessionManager  extends Thread
implements  ISessionManager
{
	/**
	 *  Lista degli ascoltatori del SessionManager.
	 */
	private EventListenerList _eventListenerList = new EventListenerList(); 

	/**
	 * Booleano che permette di terminare il thread.
	 */
	private boolean _sessionManagerActive = true;

	/** 
	 * Durata complessiva dello stream comunicata dal 
	 * gateway quando richiede una porta al PortProvider.
	 */
	private int _streamLength = 0;

	/**
	 *  Directory in cui verranno scritti
	 *  i file temporanei e il file target.
	 */
	private String _pathMediaFiles = "./clientTmp/";

	/**
	 *  Nome dello stream coincidente con il nome
	 *  del file target.
	 */
	private String _streamName = null;


	/**
	 *  Indice usato per la creazione dei file temporanei.
	 */
	private int _indexTempFile = -1;

	/**
	 *  Formato dei file temporanei e del target.
	 */
	private final Format _formatSelected = 
		new AudioFormat(AudioFormat.LINEAR, 22050, 16, 1, 1, 1);

	/**
	 * Oggetto utilizzato per l'accesso sincronizzato al vettore 
	 * _streamManagerVector.
	 */
	private Object _syncStream = new Object();

	private Object _syncClose = new Object();

	private Object _syncPartialComplete = new Object();

	/**
	 * Istante iniziale dello stream attuale.
	 * Se è diverso da zero è stato richiesta 
	 * una posizione al gateway richiamando 
	 * il metodo sendNewStartTime. 
	 */
	private int _startTime = 0;

	/**
	 * File in cui vengono copiati, in sequenza,
	 * i file temporanei.
	 */
	private File _fileTarget = null;

	/**
	 * Estensione del file target e di quelli temporanei.
	 */
	private String _fileExtension = ".au";

	/**
	 * Vettore di thread StreamManager.
	 */
	private Vector<StreamManager> _streamManagerVector = null;

	/**
	 * Streamcontainer corrente, il primo elemento del vettore
	 * _streamManagerVector.
	 */
	private StreamManager _streamManager = null;

	private boolean _changeStartTime = false;


	/**
	 * @return the _streamName
	 */
	public String getStreamName()
	{
		return _streamName;
	}

	public SessionManager(int streamLength, String streamName) 
	{
		_streamLength = streamLength;

		_streamName = streamName;

		_streamManagerVector = new Vector<StreamManager>();
	}

	/*
	 * Metodo che permette di aggiungere un nuovo stream alla sessione
	 * corrente. Il parametro rappresenta la porta del canale dati.
	 */
	public void addStream(String btPort)
	{
		/**
		 * Indice del file temporaneo in cui verrà scritto lo stream ricevuto.
		 */

		_indexTempFile = _indexTempFile + 1;

		StreamManager streamManager = 
			new StreamManager(this, btPort, _pathMediaFiles, 
					_streamName, _fileExtension,_indexTempFile, 
					getFileTarget(), _formatSelected, _startTime);

		synchronized (_syncStream) 
		{
			_streamManagerVector.addElement(streamManager);

			_syncStream.notify();
		}

	}


	private File getFileTarget()
	{
		if(_fileTarget == null)
		{
			/**
			 *  File target in cui verranno copiati i file temporanei.
			 */
			_fileTarget = new File(_pathMediaFiles + _streamName + ".au");

			if(_fileTarget.length() > 0)
			{
				/**
				 *  Se il file target esiste lo cancello.
				 */
				_fileTarget.delete();

				try
				{
					_fileTarget.createNewFile();
				}
				catch (IOException e1)
				{
					e1.printStackTrace();
				}
			}

		}
		return _fileTarget;
	}

	/**
	 * Metodo che permette di inviare al gateway una stringa 
	 * contenente il valore del nuovo startTime.
	 * Se lo start time non può essere spedito al gateway allora
	 *  restituisce false, altrimenti true.
	 */
	public boolean sendNewStartTime(int newStartTime)
	{
		boolean newStartTimeSent = false;

		if(_streamManager != null)
		{
			if(_streamManager.isPartialCompleted() == true)
			{	
				_changeStartTime = true;
				newStartTimeSent = true;

				synchronized(_syncPartialComplete)
				{
					_syncPartialComplete.notify();
				}
			}
			else
			{
				newStartTimeSent = _streamManager.sendNewStartTime(newStartTime);
			}

			if( newStartTimeSent == true)
			{
				_startTime = newStartTime;
			}
		}

		return newStartTimeSent;
	}

	public void run() 
	{
		while(_sessionManagerActive == true)
		{
			if(_streamManagerVector.isEmpty() == true)
			{
				/**
				 *  Se il primo stream non è disponibile
				 *  metto il thread in wait sull'oggetto
				 *  _syncStream.
				 */
				waitStream();
			}

			if(_sessionManagerActive == false)
			{
				return;
			}

			/**
			 * Estraggo il primo elemento del vettore ed invoco il metodo start.
			 */
			_streamManager = _streamManagerVector.firstElement();

			if(_changeStartTime == true)
			{
				_changeStartTime = false;
				_streamManager.resetFileTarget();
			}

			Thread thread = new Thread(_streamManager);
			thread.start();

			try
			{
				thread.join();
			} 
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}

			synchronized(_syncClose)
			{
				_streamManagerVector.removeElement(_streamManager);
			}

			if(_streamManager.isPartialCompleted() == true)
			{
				synchronized(_syncPartialComplete)
				{
					while(_changeStartTime == false && _sessionManagerActive == true)
					{
						try
						{
							_syncPartialComplete.wait();
						} 
						catch (InterruptedException e)
						{
							e.printStackTrace();
						}
					}
				}
			}
			else
			{
				if(_streamManager.isCompleted() == true)
				{
					close();
				}
			}

			try
			{
				sleep(1000);
			} 
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}

		}

	}// fine del metodo run

	private void waitStream()
	{
		synchronized (_syncStream) 
		{
			while(_streamManagerVector.isEmpty() == true && _sessionManagerActive == true)
			{
				try
				{
					_syncStream.wait();	
				}	
				catch (InterruptedException e) 
				{
					e.printStackTrace();
				}
			}

		}
	}

	public synchronized void close()
	{	
		if(_sessionManagerActive == true)
		{
			_sessionManagerActive = false;

			synchronized(_syncClose)
			{
				for(StreamManager streamManager: _streamManagerVector)
				{
					streamManager.close();
				}
			}

			synchronized (_syncStream) 
			{
				_syncStream.notify();
			}

			synchronized(_syncPartialComplete)
			{
				_syncPartialComplete.notify();
			}
		}

	}

	/**
	 * Metodi che permettono ai listener di registrarsi per intercettare
	 * gli eventi generati dal SessionManager.
	 */
	public void addSessionManagerListener(SessionManagerListener listener)
	{
		_eventListenerList.add(SessionManagerListener.class, listener);
	}

	public void removeSessionManagerListener(SessionManagerListener listener)
	{
		_eventListenerList.remove(SessionManagerListener.class, listener);
	}

	/**
	 * Lancia l'evento che indica all'interfaccia grafica la presenza 
	 * di un nuovo stream.
	 */
	public void fireNewStream()
	{
		NewStreamEvent newStreamEvent = 
			new NewStreamEvent(this, getFileTarget().getAbsolutePath(), 
					_streamName, _streamLength, _startTime);

		/**
		 * Per ogni listener che si e' precedentemente registrato all'evento
		 * invoco il metodo newStream passando come parametro 
		 * l'evento newStreamEvent.
		 */
		Object[] listenersArray = _eventListenerList.getListenerList();

		for(int i = listenersArray.length - 2; i >= 0; i = i - 2)
		{
			if(listenersArray[i] == SessionManagerListener.class)
			{
				((SessionManagerListener)listenersArray[i+1])
				.newStream(newStreamEvent);
			} 
		}

	}

	public void fireNewDataReceived()
	{
		int fileLength = -1;

		try
		{
			fileLength = FileAudio.getInstance().getDurationSeconds(_fileTarget);
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}

		if(fileLength != -1)
		{
			NewDataReceivedEvent newDataReceivedEvent = 
				new NewDataReceivedEvent(this, _streamName, _streamLength, fileLength, _startTime);

			/**
			 * Per ogni listener che si e' precedentemente registrato all'evento
			 * invoco il metodo newDataReceived passando come parametro 
			 * l'evento newDataReceivedEvent.
			 */
			Object[] listenersArray = _eventListenerList.getListenerList();

			for(int i = listenersArray.length - 2; i >= 0; i = i - 2)
			{
				if(listenersArray[i] == SessionManagerListener.class)
				{
					((SessionManagerListener)listenersArray[i+1])
					.newDataReceived(newDataReceivedEvent);
				} 
			}
		}

	}

	public void fireStreamError()
	{
		close();

		StreamErrorEvent streamErrorEvent = 
			new StreamErrorEvent(this, _streamName);

		/**
		 * Per ogni listener che si e' precedentemente registrato all'evento
		 * invoco il metodo streamError passando come parametro 
		 * l'evento streamErrorEvent.
		 */
		Object[] listenersArray = _eventListenerList.getListenerList();

		for(int i = listenersArray.length - 2; i >= 0; i = i - 2)
		{
			if(listenersArray[i] == SessionManagerListener.class)
			{
				((SessionManagerListener)listenersArray[i+1])
				.streamError(streamErrorEvent);
			} 
		}
	}
}

